package mspbots.cw.callbacks.core.notice;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.log4j.Log4j2;
import mspbots.cw.callbacks.core.AbstractToolsUtil;
import mspbots.cw.callbacks.core.CallbackMessage;
import mspbots.cw.common.RabbitKeys;
import mspbots.cw.common.ResultMsg;
import mspbots.cw.common.ResultSync;
import mspbots.cw.common.callback.CallbackAction;
import mspbots.cw.common.callback.CallbackEntityType;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import javax.crypto.Mac;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.UUID;

/**
 * mspbots.cw.callbacks.core.CompanyNoticeServiceImpl
 *
 * @author <a href="https://github.com/vnobo">Alex bob</a>
 * @date Created by 2020/11/17
 */
@Log4j2
@Service
public class NoticeServiceImpl extends AbstractToolsUtil implements NoticeService {

    private final WebClient webClient;

    public NoticeServiceImpl(WebClient.Builder builder) {
        this.webClient = builder.build();
    }

    @Override
    public Long sender(CallbackMessage callbackMessage) {

        String callBackName = callbackMessage.getType().name().toUpperCase();
        ResultSync resultSync = ResultSync.withDefault(callBackName + "-CALLBACK");
        resultSync.setTenantId(callbackMessage.getTenantId());
        resultSync.setParams(this.objectMapper.convertValue(callbackMessage, JsonNode.class));
        resultSync.setCount(1);

        try {

            this.resultMsg(resultSync.getRequestId(), callbackMessage);

            resultSync.setStatus(HttpStatus.OK);
            resultSync.setMsg("Call back ticket success. id [" + callbackMessage.getId() + "]");

        } catch (JsonProcessingException e) {
            resultSync.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
            resultSync.setMsg(e.getMessage());
        }

        resultSync.setElapsedTime(System.currentTimeMillis() - resultSync.getElapsedTime());
        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, resultSync);
        return callbackMessage.getId();
    }

    @Override
    public Mono<Boolean> verifying(Mono<String> bodyMono, String signature) {
        return bodyMono.flatMap(body -> {
            try {
                return Mono.just(super.objectMapper.readValue(body, JsonNode.class))
                        .flatMap(bodyNode -> this.webClient.get()
                                .uri(bodyNode.findValue("key_url").asText())
                                .retrieve()
                                .bodyToMono(String.class))
                        .map(result -> {
                            try {
                                JsonNode resultNode = super.objectMapper.readValue(result, JsonNode.class);
                                String hashStr = this.computeHash(body, resultNode.get("signing_key").asText());
                                log.debug("Compute hash sign [{}] str [{}]",signature, hashStr);
                                return signature.equals(hashStr);
                            } catch (JsonProcessingException e) {
                                return false;
                            }
                        });
            } catch (IOException e) {
                return Mono.just(false);
            }
        }).onErrorResume(err -> {
            log.error("Json read value error msg: {}", err.getMessage());
            return Mono.just(false);
        });
    }

    private String computeHash(String body, String signingKey) {
        log.debug("Hash key: {}", signingKey);
        HMac mac = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, signingKey.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(mac.digest(body.getBytes(StandardCharsets.UTF_8)));
    }

    private void resultMsg(UUID requestId, CallbackMessage callbackMessage) throws JsonProcessingException {
        ResultMsg resultMsg = ResultMsg.builder().id(callbackMessage.getId()).requestId(requestId)
                .tenantId(callbackMessage.getTenantId())
                .action(callbackMessage.getAction().name()).syncDate(LocalDateTime.now()).build();
        if (callbackMessage.getAction() == CallbackAction.deleted) {
            resultMsg.setEntity(this.objectMapper.convertValue(callbackMessage, JsonNode.class));
        } else {
            resultMsg.setEntity(this.objectMapper.readValue(callbackMessage.getEntity(), JsonNode.class));
        }

        if (callbackMessage.getType() == CallbackEntityType.company) {
            this.sendEntity(RabbitKeys.COMPANIES_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.ticket) {
            this.sendEntity(RabbitKeys.TICKETS_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.schedule) {
            this.sendEntity(RabbitKeys.SCHEDULES_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.invoice) {
            this.sendEntity(RabbitKeys.INVOICES_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.contact) {
            this.sendEntity(RabbitKeys.CONTACTS_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.activity) {
            this.sendEntity(RabbitKeys.ACTIVITIES_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.agreement) {
            this.sendEntity(RabbitKeys.AGREEMENTS_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.project) {
            this.sendEntity(RabbitKeys.PROJECTS_PHASES_SYNC_QUEUE_KEY, resultMsg);
        } else if (callbackMessage.getType() == CallbackEntityType.configuration) {
            this.sendEntity(RabbitKeys.CONFIGURATIONS_SYNC_QUEUE_KEY, resultMsg);
        } else {
            log.warn("Call back entity no queue, type: [{}],entity: [{}]",
                    callbackMessage.getTenantId(), callbackMessage.getId());
        }

    }

}
